import 'reflect-metadata'
import { Container, inject, injectable, interfaces, optional, postConstruct } from 'inversify';

export interface Warrior {
  fight(): string;

  sneak(): string;
}

export interface Weapon {
  hit(): string;
}

export interface ThrowableWeapon {
  throw(): string;
}

const TYPES = {
  Warrior: Symbol.for("Warrior"),
  Weapon: Symbol.for("Weapon"),
  ThrowableWeapon: Symbol.for("ThrowableWeapon")
};

// -------------------------------------------------------------------------------------------------------------------------

@injectable()
export class Katana implements Weapon {
  public constructor() {
    console.log("constructor - Katana");
  }

  public hit() {
    return "cut!";
  }
}

@injectable()
export class Shuriken implements ThrowableWeapon {
  public constructor() {
    console.log("constructor - Shuriken");
  }

  public throw() {
    return "hit!";
  }
}

// @injectable()
// export class Ninja implements Warrior {
//   _katana: Weapon;
//   _shuriken: ThrowableWeapon;
//   public constructor(
//     @inject(TYPES.Weapon) @optional() katana: Weapon,
//     @inject(TYPES.ThrowableWeapon) @optional() shuriken: ThrowableWeapon
//   ) {
//     this._katana = katana;
//     this._shuriken = shuriken;
//   }
//   public fight() { return this._katana.hit(); }
//   public sneak() { return this._shuriken.throw(); }
// }

@injectable()
export class Ninja implements Warrior {
  @inject(TYPES.Weapon)
  @optional()
  _katana!: Weapon;

  @inject(TYPES.ThrowableWeapon)
  @optional()
  _shuriken!: ThrowableWeapon;

  public constructor() {
    console.log("constructor - Ninja");
  }

  public fight() {
    console.log("# 1--fight")
    return this._katana.hit();
  }

  public sneak() {
    return this._shuriken.throw();
  }

  @postConstruct()
  public testMethod() {
    console.log("postConstruct --- ##")
  }
}


// -------------------------------------------------------------------------------------------------------------------------

function logger(planAndResolve: interfaces.Next): interfaces.Next {
  return (args: interfaces.NextArgs) => {
    let start = new Date().getTime();
    let result = planAndResolve(args);
    let end = new Date().getTime();
    console.log(`############## wooooo  ${end - start}`);
    return result;
  };
}


const myContainer = new Container({defaultScope: 'Singleton'});
myContainer.applyMiddleware(logger);
myContainer.bind<Ninja>(Ninja).to(Ninja).inTransientScope().onActivation((context, ninja) => {
  console.log("ninja ----> ", ninja);
  let handler = {
    apply: function (target: any, thisArgument: any, argumentsList: any) {
      console.log(`Starting: ${new Date().getTime()}`);
      let result = target.apply(thisArgument, argumentsList);
      console.log(`Finished: ${new Date().getTime()}`);
      return result;
    }
  };
  ninja.fight = new Proxy(ninja.fight, handler);
  return ninja;
});
console.log("# 1--1")
// test();
myContainer.bind<Weapon>(TYPES.Weapon).to(Katana);
console.log("# 1--2")
// test();
myContainer.bind<ThrowableWeapon>(TYPES.ThrowableWeapon).to(Shuriken);
console.log("# 1--3")
test();
test();

// -------------------------------------------------------------------------------------------------------------------------

function test() {
  console.log("# 1")
  const ninja = myContainer.get<Ninja>(Ninja);
  console.log("# 2")

  console.log("ninja._katana ", ninja._katana ? ninja.fight() : "null");
  console.log("ninja._shuriken ", ninja._shuriken ? ninja.sneak() : "null");

  console.log("#-----------------------------------------------");
}

const nn = new Ninja()
nn._katana = new Katana()
nn.fight()
